package com.cloud.async.business.context;

import com.cloud.async.business.body.BusinessResult;
import com.cloud.async.business.body.FutureResult;
import com.cloud.async.business.core.AsyncResultProvider;
import com.cloud.async.business.core.AsyncTaskLifecycle;
import com.cloud.async.business.core.BaseAsyncResultHandler;
import com.cloud.async.flow.util.timer.SystemClock;
import com.cloud.async.result.core.handler.DefaultAsyncResultHandlerWrapper;
import com.cloud.async.retry.DefaultRetryPolicy;
import com.cloud.async.retry.RetryPolicy;
import com.cloud.async.util.ClassAsyncResultUtils;
import com.cloud.async.util.ex.AsyncException;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * @author chengrui
 * @title: AsyncTaskContext
 * @projectName webCloudAsync
 * @description: 相关方法的核心
 * @date 2021/9/23 23:31
 */
public abstract class AsyncTaskContext extends AsyncTaskLifecycle implements AsyncResultProvider {


    protected volatile RetryPolicy retryPolicy;


    protected void initRetryPolicy(RetryPolicy rpy) {
        this.retryPolicy = rpy;
    }


    /**
     *
     * @param task
     * @param taskId
     * @param rsp
     * @param executor
     * @param <T>
     * @return
     */
    protected <T> BusinessResult businessLogic(Supplier<T> task, String taskId, Class<T> rsp, Executor executor) {

        // 1. 校验重试策略配置
        int maxRetries = validateAndGetMaxRetries();
        List<Long> backoffDelays = getBackoffDelays();

        Exception lastException = null;
        int attempt = 0; // 当前尝试次数（从0开始计数）

        while (attempt <= maxRetries) {
            try {
                // 2. 执行任务（异步）
                CompletableFuture<Object> result = CompletableFuture.supplyAsync(task::get, executor);
                long taskTimestamp = SystemClock.now();
                FutureResult futureResult = new FutureResult(rsp, taskTimestamp, result);
                registerTask(taskId, futureResult);

                // 成功则直接返回
                return new BusinessResult(taskId, rsp, taskTimestamp);

            } catch (Exception e) {
                lastException = e;
                attempt++;

                // 3. 判断是否还需重试
                if (attempt > maxRetries) {
                    break;
                }

                // 4. 计算退避延迟时间
                long sleepTime = calculateBackoffDelay(attempt, backoffDelays);
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new AsyncException("Retry process interrupted", ie);
                }
            }
        }

        // 5. 所有重试失败后返回错误结果
        return new BusinessResult(
                taskId,
                rsp,
                SystemClock.now(),
                "All retries failed. Last error: " + lastException.getMessage()
        );
    }

    // 校验并获取最大重试次数
    private int validateAndGetMaxRetries() {
        if (this.retryPolicy == null) {
            synchronized (this) {
                if (this.retryPolicy == null) {
                    retryPolicy = new DefaultRetryPolicy();
                }
            }
        }
        int maxRetries = retryPolicy.getMaxAttempts();
        if (maxRetries < 0) {
            throw new AsyncException(
                    "Invalid retry configuration: maxAttempts (" + maxRetries + ") must be ≥0. " +
                            "Please specify a non-negative value."
            );
        }
        return maxRetries;
    }

    // 获取退避延迟列表（确保不为null）
    private List<Long> getBackoffDelays() {
        List<Long> delays = retryPolicy.getBackoffDelay();
        return delays != null ? delays : Collections.emptyList();
    }

    // 计算当前重试的延迟时间
    private long calculateBackoffDelay(int attempt, List<Long> backoffDelays) {
        if (backoffDelays.isEmpty()) {
            return 100L; // 默认延迟
        }

        // 数组索引从0开始，attempt从1开始计数
        int delayIndex = Math.min(attempt - 1, backoffDelays.size() - 1);
        return backoffDelays.get(delayIndex);
    }

}
